package org.cogaen.sound;

import java.util.LinkedList;
import java.util.Vector;

/**
 * @class ThreadPool
 * @author Florian Lettner
 *
 * A thread pool that can be used to manage multiple threads at the same time.
 */
public abstract class ThreadPool extends ThreadGroup {
	
	/** A flag to indicate if the thread pool is alive. */
	private boolean isAlive;
	
	/** A linked list representing the task queue. */
	private LinkedList<Runnable> taskQueue;
	
	/** The current thread id of a thread. */
	private int threadID;
	
	/** The id of the thread pool. */
	private static int threadPoolID;
	
	/** An array of already used thread pool names. */
	private static Vector<String> threadPoolNames = new Vector<String>();

	/**
	 * Creates a new thread pool with a certain name. This name must be unique.
	 * 
	 * @param name The name of the thread pool.
	 * @param numThreads The amount of threads to be pooled.
	 */
	public ThreadPool(String name, int numThreads) {
		super(name);
		
		if(threadPoolNames.contains(name)) {
			throw new RuntimeException("A threadpool with the name " + name + " already exists.");
		}

		setDaemon(true);
		isAlive = true;
		taskQueue = new LinkedList<Runnable>();
		for (int i = 0; i < numThreads; i++) {
			new PooledThread().start();
		}
	}
	
	/**
	 * Create a thread pool with dynamic names.
	 * @param numThreads The amount of threads to be pooled.
	 */
	public ThreadPool(int numThreads) {
		super("ThreadPool-" + (threadPoolID++));
		setDaemon(true);
		isAlive = true;
		taskQueue = new LinkedList<Runnable>();
		for (int i = 0; i < numThreads; i++) {
			new PooledThread().start();
		}
	}

	/**
	 * Provides the caller with the next task.
	 * 
	 * @return The next task to be processed.
	 * @throws InterruptedException
	 */
	protected synchronized Runnable getTask() throws InterruptedException {
		while (taskQueue.size() == 0) {
			if (!isAlive) {
				return null;
			}
			wait();
		}
		return taskQueue.removeFirst();

	}

	/**
	 * Runs a new task.
	 * 
	 * @param task The task to be run.
	 */
	public synchronized void runTask(Runnable task) {
		if (!isAlive)
			throw new IllegalStateException();
		if (task != null) {
			taskQueue.add(task);
			notify();
		}
	}
	
	/**
	 * Removes an existing task.
	 * 
	 * @param task The task to be removed.
	 */
	public synchronized void stopTask(Runnable task) {
		if(!isAlive)
			throw new IllegalStateException();
		if (task != null) {
			taskQueue.remove(task);
			notify();
		}
	}

	/**
	 * Closes the thread pool immediately.
	 */
	public synchronized void close() {
		if (isAlive) {
			isAlive = false;
			taskQueue.clear();
			interrupt();
		}
	}

	/**
	 * Waits for active threads to be finished and then closes the pool.
	 */
	public void join() {
		synchronized (this) {
			isAlive = false;
			notifyAll();
		}
		Thread[] threads = new Thread[activeCount()];
		int count = enumerate(threads);
		for (int i = 0; i < count; i++) {
			try {
				threads[i].join();
			} catch (InterruptedException ex) {
			}
		}
	}
	
	/** 
	 * Is called after a thread started.
	 */
	protected abstract void threadStarted();
	
	/**
	 * Is called after a thread stopped.
	 */
	protected abstract void threadStopped();

	/**
	 * @class PooledThread
	 * @author Florian Lettner
	 *
	 * Represents a thread that belongs to a certain thread pool.
	 */
	private class PooledThread extends Thread {
		public PooledThread() {
			super(ThreadPool.this, "PooledThread-" + (threadID++));
		}

		@Override
		public void run() {
			
			// signal that this thread has started
			threadStarted();
			
			while (!isInterrupted()) {
				Runnable task = null;
				try {
					task = getTask();
				} catch (InterruptedException ex) {
				}
				if (task == null)
					return;
				try {
					task.run();
				} catch (Throwable t) {
					uncaughtException(this, t);
				}
			}
			
			// signal that this thread has stopped
			threadStopped();
		}
		
	}
}
